package com.kokotchy.tuxcontrol;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * Thread that monitor modification of the tux
 * 
 * $Date: 2009-12-30 15:09:35 +0000 (Wed, 30 Dec 2009) $
 * 
 * @author $Author: kokotchy $
 * @version $Revision: 18 $
 */
public class TuxStatus extends Thread {

	/**
	 * TAG used when debugging
	 */
	private static final String TAG = "TuxStatus";

	/**
	 * The elements of the tux
	 */
	private HashMap<Integer, Integer> elements;

	/**
	 * Handler to communicate with the main thread
	 */
	private Handler mHandler;

	/**
	 * Host to use
	 */
	private String mHost;

	/**
	 * Port to use
	 */
	private int mPort;

	/**
	 * If the thread have to stop
	 */
	private volatile boolean stop;

	/**
	 * Create a new TuxStatus
	 * 
	 * @param handler
	 *            Handler to communicate with the main thread
	 * @param host
	 *            Host of the server
	 * @param port
	 *            Port of the server
	 */
	public TuxStatus(Handler handler, String host, int port) {
		mHandler = handler;
		mHost = host;
		mPort = port;
		stop = false;
		elements = new HashMap<Integer, Integer>();
		// elements.put(TuxConsts.FLIPPERS_STATE, getFlippers());
		// elements.put(TuxConsts.RIGHT_LED_STATE, getRightLed());
		// elements.put(TuxConsts.LEFT_LED_STATE, getLeftLed());
		// elements.put(TuxConsts.EYES_STATE, getEyes());
		// elements.put(TuxConsts.MOUTH_STATE, getMouth());
		// elements.put(TuxConsts.CHARGER_STATE, getCharger());
		// elements.put(TuxConsts.SPINNING_STATE, getSpinDirection());
		elements.put(TuxConsts.FLIPPERS_STATE, -1);
		elements.put(TuxConsts.RIGHT_LED_STATE, -1);
		elements.put(TuxConsts.LEFT_LED_STATE, -1);
		elements.put(TuxConsts.EYES_STATE, -1);
		elements.put(TuxConsts.MOUTH_STATE, -1);
		elements.put(TuxConsts.CHARGER_STATE, -1);
		elements.put(TuxConsts.SPINNING_STATE, -1);
		elements.put(TuxConsts.BATTERY_LEVEL, -1);
		elements.put(TuxConsts.LIGHT_LEVEL, -1);
	}

	/**
	 * Close the connection
	 */
	public void close() {
		stop = true;
	}

	@Override
	public void run() {
		Log.i(TAG, "Start run");
		HashMap<Integer, Integer> list;
		while (!stop) {
			list = new HashMap<Integer, Integer>();
			for (Integer part : elements.keySet()) {
				int oldValue = elements.get(part);
				int currentValue = -1;
				switch (part) {
				case TuxConsts.EYES_STATE:
					currentValue = getEyes();
					break;
				case TuxConsts.FLIPPERS_STATE:
					currentValue = getFlippers();
					break;
				case TuxConsts.LEFT_LED_STATE:
					currentValue = getLeftLed();
					break;
				case TuxConsts.RIGHT_LED_STATE:
					currentValue = getRightLed();
					break;
				case TuxConsts.MOUTH_STATE:
					currentValue = getMouth();
					break;
				case TuxConsts.CHARGER_STATE:
					currentValue = getCharger();
					break;
				case TuxConsts.SPINNING_STATE:
					currentValue = getSpinDirection();
					break;
				case TuxConsts.BATTERY_LEVEL:
					currentValue = getBatteryLevel();
					break;
				case TuxConsts.LIGHT_LEVEL:
					currentValue = getLightLevel();
					break;
				}

				if (currentValue != oldValue) {
					list.put(part, currentValue);
				}
			}
			if (list.size() > 0) {
				notifyModification(list);
			}

			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Return the level of the battery
	 * 
	 * @return level of the battery
	 */
	private int getBatteryLevel() {
		String statusValue = getStatusValue("battery_level");
		if (statusValue == null) {
			return 0;
		}
		return Integer.parseInt(statusValue);
	}

	/**
	 * Return the state of the charger
	 * 
	 * @return State of the charger
	 */
	private int getCharger() {
		String statusValue = getStatusValue("charger_state");
		if (statusValue == null) {
			Integer previous = elements.get(TuxConsts.CHARGER_STATE_NAME);
			if (previous == null) {
				return TuxConsts.CHARGER_CHARGING;
			} else {
				return previous;
			}
		}
		if (statusValue.equals("CHARGING")) {
			return TuxConsts.CHARGER_CHARGING;
		} else if (statusValue.equals("TRICKLE")) {
			return TuxConsts.CHARGER_TRICKLE;
		} else {
			return TuxConsts.CHARGER_DISCHARGING;
		}
	}

	/**
	 * Return the position of the eyes
	 * 
	 * @return Position of the eyes
	 */
	private int getEyes() {
		String statusValue = getStatusValue("eyes_position");
		if (statusValue == null) {
			Integer previous = elements.get(TuxConsts.EYES_STATE_NAME);
			if (previous == null) {
				return TuxConsts.EYES_STATE_CLOSE;
			} else {
				return previous;
			}
		}
		return statusValue.equals("CLOSE") ? TuxConsts.EYES_STATE_CLOSE
				: TuxConsts.EYES_STATE_OPEN;
	}

	/**
	 * Return the position of the flippers
	 * 
	 * @return Position of the flippers
	 */
	private int getFlippers() {
		String statusValue = getStatusValue("flippers_position");
		if (statusValue == null) {
			Integer previous = elements.get(TuxConsts.FLIPPERS_STATE_NAME);
			if (previous == null) {
				return TuxConsts.FLIPPERS_STATE_DOWN;
			} else {
				return previous;
			}
		}
		return statusValue.equals("DOWN") ? TuxConsts.FLIPPERS_STATE_DOWN
				: TuxConsts.FLIPPERS_STATE_UP;
	}

	/**
	 * Return the state of the led
	 * 
	 * @param side
	 *            Side of the led
	 * @return State of the specified led
	 */
	private int getLed(String side) {
		String statusValue = getStatusValue(side + "_led_state");
		if (statusValue == null) {
			return -1;
		}
		return statusValue.equals("OFF") ? TuxConsts.LED_STATE_OFF
				: TuxConsts.LED_STATE_ON;
	}

	/**
	 * Return the status of the left led
	 * 
	 * @return Status of the left led
	 */
	private int getLeftLed() {
		int status = getLed("left");
		if (status < 0) {
			Integer previous = elements.get(TuxConsts.LEFT_LED_STATE_NAME);
			if (previous == null) {
				return TuxConsts.LED_STATE_OFF;
			} else {
				return previous;
			}
		}
		return status;
	}

	private int getLightLevel() {
		String statusValue = getStatusValue("light_level");
		if (statusValue == null) {
			return 0;
		}
		return (int) Float.parseFloat(statusValue);
	}

	/**
	 * Return the position of the mouth
	 * 
	 * @return Position of the mouth
	 */
	private int getMouth() {
		String statusValue = getStatusValue("mouth_position");
		if (statusValue == null) {
			Integer previous = elements.get(TuxConsts.MOUTH_STATE_NAME);
			if (previous == null) {
				return TuxConsts.MOUTH_STATE_OPEN;
			} else {
				return previous;
			}
		}
		return statusValue.equals("OPEN") ? TuxConsts.MOUTH_STATE_OPEN
				: TuxConsts.MOUTH_STATE_CLOSE;
	}

	/**
	 * Return the string name of the given element
	 * 
	 * @param element
	 *            Element of the tux
	 * @return String name of the element
	 */
	private String getName(Integer element) {
		String name = "";
		switch (element) {
		case TuxConsts.EYES_STATE:
			name = TuxConsts.EYES_STATE_NAME;
			break;
		case TuxConsts.FLIPPERS_STATE:
			name = TuxConsts.FLIPPERS_STATE_NAME;
			break;
		case TuxConsts.LEFT_LED_STATE:
			name = TuxConsts.LEFT_LED_STATE_NAME;
			break;
		case TuxConsts.RIGHT_LED_STATE:
			name = TuxConsts.RIGHT_LED_STATE_NAME;
			break;
		case TuxConsts.MOUTH_STATE:
			name = TuxConsts.MOUTH_STATE_NAME;
			break;
		case TuxConsts.CHARGER_STATE:
			name = TuxConsts.CHARGER_STATE_NAME;
			break;
		case TuxConsts.SPINNING_STATE:
			name = TuxConsts.SPINNING_NAME;
			break;
		case TuxConsts.BATTERY_LEVEL:
			name = TuxConsts.BATTERY_LEVEL_NAME;
			break;
		case TuxConsts.LIGHT_LEVEL:
			name = TuxConsts.LIGHT_LEVEL_NAME;
			break;
		}
		return name;
	}

	/**
	 * Return the state of the right led
	 * 
	 * @return State of the right led
	 */
	private int getRightLed() {
		int status = getLed("right");
		if (status < 0) {
			Integer previous = elements.get(TuxConsts.RIGHT_LED_STATE_NAME);
			if (previous == null) {
				return TuxConsts.LED_STATE_OFF;
			} else {
				return previous;
			}
		}
		return status;
	}

	/**
	 * Return the direction of the spinning
	 * 
	 * @return Direction of the spinning
	 */
	private int getSpinDirection() {
		String statusValue = getStatusValue("spinning_direction");
		if (statusValue == null) {
			Integer previous = elements.get(TuxConsts.SPINNING_NAME);
			if (previous == null) {
				return TuxConsts.SPIN_NONE;
			} else {
				return previous;
			}
		}

		if (statusValue.equals("RIGHT")) {
			return TuxConsts.SPIN_RIGHT;
		} else if (statusValue.equals("LEFT")) {
			return TuxConsts.SPIN_LEFT;
		}
		return TuxConsts.SPIN_NONE;
	}

	/**
	 * Return the value of a status
	 * 
	 * @param status
	 *            Status to retrieve
	 * @return Value of the status
	 */
	private String getStatusValue(String status) {
		Document document = sendRequest("/0/status/request_one?status_name="
				+ status, true);
		if (document == null) {
			return null;
		}
		Node root = document.getFirstChild();
		Node data = root.getFirstChild();
		Node value = data.getLastChild();
		for (int i = 0; i < value.getChildNodes().getLength(); i++) {
			Node item = value.getChildNodes().item(i);
			if (item.getNodeType() == Node.TEXT_NODE) {
				return item.getNodeValue();
			}
		}
		return null;
	}

	/**
	 * Notify the main thread of modification
	 * 
	 * @param list
	 *            List of elements that are modified and their new value
	 */
	private void notifyModification(HashMap<Integer, Integer> list) {
		Message msg = mHandler.obtainMessage();
		Bundle b = new Bundle();
		for (Integer element : list.keySet()) {
			int value = list.get(element);
			elements.put(element, value);
			String name = getName(element);
			b.putInt(name, value);
		}
		msg.setData(b);
		mHandler.sendMessage(msg);
	}

	/**
	 * Send a request to the server. If result is true, return the DOM Document,
	 * otherwise, return null
	 * 
	 * @param request
	 *            Request to send to the server
	 * @param result
	 *            If the result matter
	 * @return If result, the DOM Document, otherwise, null
	 */
	private Document sendRequest(String request, boolean result) {
		try {
			Document document = null;
			// thanks url to remove ? ... adding empty at the end to be sure
			// that the command is executed
			if (request.charAt(request.length() - 1) == '?') {
				request += "empty";
				// Log.d(TAG, "Thank you bitch... new command is: " + request);
			}
			URL url = new URL(String.format("http://%s:%d%s", mHost, mPort,
					request));
			URLConnection connection = url.openConnection();

			if (result) {
				DocumentBuilderFactory builderFactory = DocumentBuilderFactory
						.newInstance();
				try {
					DocumentBuilder builder = builderFactory
							.newDocumentBuilder();
					document = builder.parse(new InputSource(connection
							.getInputStream()));
				} catch (ParserConfigurationException e) {
					Log.e(TAG, "Error!", e);
					return null;
				} catch (SAXException e) {
					Log.e(TAG, "End of document.", e);
					return null;
				}
			} else {
				String requestResult = "";
				String line = "";
				BufferedReader reader = new BufferedReader(
						new InputStreamReader(connection.getInputStream()));
				while ((line = reader.readLine()) != null) {
					requestResult += line;
				}

				Log.d(TAG, requestResult);

			}
			return document;
		} catch (UnknownHostException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}
}
